/*
 * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau <kossebau@kde.org>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

import QtQuick 2.9

import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore

import org.kde.plasma.private.weather 1.0

Item {
    id: root

    Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground

    readonly property string weatherSource: plasmoid.nativeInterface.source
    readonly property int updateInterval: plasmoid.nativeInterface.updateInterval
    readonly property int displayTemperatureUnit: plasmoid.nativeInterface.displayTemperatureUnit
    readonly property int displaySpeedUnit: plasmoid.nativeInterface.displaySpeedUnit
    readonly property int displayPressureUnit: plasmoid.nativeInterface.displayPressureUnit
    readonly property int displayVisibilityUnit: plasmoid.nativeInterface.displayVisibilityUnit

    property bool connectingToSource: false
    readonly property bool needsConfiguration: !generalModel.location && !connectingToSource

    readonly property int invalidUnit: -1 //TODO: make KUnitConversion::InvalidUnit usable here

    // model providing final display strings for observation properties
    readonly property var observationModel: {
        var model = {};
        var data = weatherDataSource.currentData || {};

        function getNumber(key) {
            var number = data[key];
            if (typeof number === "string") {
                var parsedNumber = parseFloat(number);
                return isNaN(parsedNumber) ? null : parsedNumber;
            }
            return (typeof number !== "undefined") && (number !== "") ? number : null;
        }
        function getNumberOrString(key) {
            var number = data[key];
            return (typeof number !== "undefined") && (number !== "") ? number : null;
        }

        var reportTemperatureUnit = data["Temperature Unit"] || invalidUnit;
        var reportPressureUnit =    data["Pressure Unit"] || invalidUnit;
        var reportVisibilityUnit =  data["Visibility Unit"] || invalidUnit;
        var reportWindSpeedUnit =   data["Wind Speed Unit"] || invalidUnit;

        model["conditions"] = data["Current Conditions"] || "";

        var conditionIconName = data["Condition Icon"] || null;
        model["conditionIconName"] = conditionIconName ? Util.existingWeatherIconName(conditionIconName) : "weather-none-available";

        var temperature = getNumber("Temperature");
        model["temperature"] = temperature !== null ? Util.temperatureToDisplayString(displayTemperatureUnit, temperature, reportTemperatureUnit) : "";

        var windchill = getNumber("Windchill");
        // Use temperature unit to convert windchill temperature
        // we only show degrees symbol not actual temperature unit
        model["windchill"] = windchill !== null ?
            Util.temperatureToDisplayString(displayTemperatureUnit, windchill, reportTemperatureUnit, false, true) :
            "";

        var humidex = getNumber("Humidex");
        // TODO: this seems wrong, does the humidex have temperature as units?
        // Use temperature unit to convert humidex temperature
        // we only show degrees symbol not actual temperature unit
        model["humidex"] = humidex !== null ?
            Util.temperatureToDisplayString(displayTemperatureUnit, humidex, reportTemperatureUnit, false, true) :
            "";

        var dewpoint = getNumber("Dewpoint");
        model["dewpoint"] = dewpoint !== null ?
            Util.temperatureToDisplayString(displayTemperatureUnit, dewpoint, reportTemperatureUnit) : "";

        var pressure = getNumber("Pressure");
        model["pressure"] = pressure !== null ?
            Util.valueToDisplayString(displayPressureUnit, pressure, reportPressureUnit, 2) : "";

        var pressureTendency = (data && data["Pressure Tendency"]) || null;
        model["pressureTendency"] =
            pressureTendency === "rising"  ? i18nc("pressure tendency", "Rising")  :
            pressureTendency === "falling" ? i18nc("pressure tendency", "Falling") :
            pressureTendency === "steady"  ? i18nc("pressure tendency", "Steady")  :
            /* else */                       "";

        var visibility = getNumberOrString("Visibility");
        model["visibility"] = visibility !== null ?
            ((reportVisibilityUnit !== invalidUnit) ?
                Util.valueToDisplayString(displayVisibilityUnit, visibility, reportVisibilityUnit, 1) : visibility) :
            "";

        var humidity = getNumber("Humidity");
        model["humidity"] = humidity !== null ? Util.percentToDisplayString(humidity) : "";

        // TODO: missing check for windDirection validness
        var windDirection = data["Wind Direction"] || "";
        var windSpeed = getNumberOrString("Wind Speed");
        var windSpeedText;
        if (windSpeed !== null && windSpeed !== "") {
            var windSpeedNumeric = (typeof windSpeed !== 'number') ? parseFloat(windSpeed) : windSpeed;
            if (!isNaN(windSpeedNumeric)) {
                if (windSpeedNumeric !== 0) {
                    windSpeedText = Util.valueToDisplayString(displaySpeedUnit, windSpeedNumeric, reportWindSpeedUnit, 1);
                } else {
                    windSpeedText = i18nc("Wind condition", "Calm");
                }
            } else {
                // TODO: i18n?
                windSpeedText = windSpeed;
            }
        }
        model["windSpeed"] = windSpeedText || "";
        model["windDirectionId"] = windDirection;
        model["windDirection"] = windDirection ? i18nc("wind direction", windDirection) : "";

        var windGust = getNumber("Wind Gust");
        model["windGust"] = windGust !== null ? Util.valueToDisplayString(displaySpeedUnit, windGust, reportWindSpeedUnit, 1) : "";

        return model;
    }

    readonly property var generalModel: {
        var model = {};
        var data = weatherDataSource.currentData || {};

        var todayForecastTokens = (data["Short Forecast Day 0"] || "").split("|");

        model["location"] =  data["Place"] || "";
        model["courtesy"] =  data["Credit"] || "";
        model["creditUrl"] = data["Credit Url"] || "";

        var forecastDayCount = parseInt(data["Total Weather Days"] || "");
        var forecastTitle;
        if (!isNaN(forecastDayCount) && forecastDayCount > 0) {
            forecastTitle = i18ncp("Forecast period timeframe", "1 Day", "%1 Days", forecastDayCount);
        }
        model["forecastTitle"] = forecastTitle || "";

        var conditionIconName = observationModel.conditionIconName;
        if (!conditionIconName ||
            conditionIconName === "weather-none-available") {

            // try icon from current weather forecast
            if (todayForecastTokens.length === 6 && todayForecastTokens[1] !== "N/U") {
                conditionIconName = Util.existingWeatherIconName(todayForecastTokens[1]);
            } else {
                conditionIconName = "weather-none-available";
            }
        }
        model["currentConditionIconName"] = conditionIconName;

        return model;
    }

    readonly property var detailsModel: {
        var model = [];

        if (observationModel.windchill) {
            model.push({
                "label": i18nc("@label", "Windchill:"),
                "text":  observationModel.windchill
            });
        };

        if (observationModel.humidex) {
            model.push({
                "label": i18nc("@label", "Humidex:"),
                "text":  observationModel.humidex
            });
        }

        if (observationModel.dewpoint) {
            model.push({
                "label": i18nc("@label ground temperature", "Dewpoint:"),
                "text":  observationModel.dewpoint
            });
        }

        if (observationModel.pressure) {
            model.push({
                "label": i18nc("@label", "Pressure:"),
                "text":  observationModel.pressure
            });
        }

        if (observationModel.pressureTendency) {
            model.push({
                "label": i18nc("@label pressure tendency, rising/falling/steady", "Pressure Tendency:"),
                "text":  observationModel.pressureTendency
            });
        }

        if (observationModel.visibility) {
            model.push({
                "label": i18nc("@label", "Visibility:"),
                "text":  observationModel.visibility
            });
        }

        if (observationModel.humidity) {
            model.push({
                "label": i18nc("@label", "Humidity:"),
                "text":  observationModel.humidity
            });
        }

        if (observationModel.windGust) {
            model.push({
                "label": i18nc("@label", "Wind Gust:"),
                "text":  observationModel.windGust
            });
        }

        return model;
    }

    readonly property var forecastModel: {
        var model = [];
        var data = weatherDataSource.currentData;

        var forecastDayCount = parseInt((data && data["Total Weather Days"]) || "");
        if (isNaN(forecastDayCount) || forecastDayCount <= 0) {
            return model;
        }

        var reportTemperatureUnit = (data && data["Temperature Unit"]) || invalidUnit;

        var dayItems = [];
        var conditionItems = [];
        var hiItems = [];
        var lowItems = [];

        for (var i = 0; i < forecastDayCount; ++i) {
            var forecastDayKey = "Short Forecast Day " + i;
            var forecastDayTokens = ((data && data[forecastDayKey]) || "").split("|");

            if (forecastDayTokens.length !== 6) {
                // We don't have the right number of tokens, abort trying
                break;
            }

            dayItems.push(forecastDayTokens[0]);

            // If we see N/U (Not Used) we skip the item
            var weatherIconName = forecastDayTokens[1];
            if (weatherIconName && weatherIconName !== "N/U") {
                var iconAndToolTip = Util.existingWeatherIconName(weatherIconName);

                iconAndToolTip += "|";

                var condition = forecastDayTokens[2];
                var probability = forecastDayTokens[5];
                if (probability !== "N/U" &&
                    probability !== "N/A" &&
                    !!probability) {
                    iconAndToolTip += i18nc("certain weather condition (probability percentage)",
                                            "%1 (%2 %)", condition, probability);
                } else {
                    iconAndToolTip += condition;
                }
                conditionItems.push(iconAndToolTip);
            }

            var tempHigh = forecastDayTokens[3];
            if (tempHigh !== "N/U") {
                if (tempHigh === "N/A" || !tempHigh) {
                    hiItems.push(i18nc("Short for no data available", "-"));
                } else {
                    hiItems.push(Util.temperatureToDisplayString(displayTemperatureUnit, tempHigh,
                                                                 reportTemperatureUnit, true));
                }
            }

            var tempLow = forecastDayTokens[4];
            if (tempLow !== "N/U") {
                if (tempLow === "N/A" || !tempLow) {
                    lowItems.push(i18nc("Short for no data available", "-"));
                } else {
                    lowItems.push(Util.temperatureToDisplayString(displayTemperatureUnit, tempLow,
                                                                  reportTemperatureUnit, true));
                }
            }
        }

        if (dayItems.length) {
            model.push(dayItems);
        }
        if (conditionItems.length) {
            model.push(conditionItems);
        }
        if (hiItems.length)  {
            model.push(hiItems);
        }
        if (lowItems.length) {
            model.push(lowItems);
        }

        return model;
    }

    readonly property var noticesModel: {
        var model = [];
        var data = weatherDataSource.currentData;

        var warnings = [];
        var warningsCount = parseInt((data && data["Total Warnings Issued"]) || "");
        if (isNaN(warningsCount)) {
            warningsCount = 0;
        }
        for (var i = 0; i < warningsCount; ++i) {
            warnings.push({
                "description": data["Warning Description "+i],
                "info":        data["Warning Info "+i]
            });
        }
        model.push(warnings);

        var watches = [];
        var watchesCount = parseInt((data && data["Total Watches Issued"]) || "");
        if (isNaN(watchesCount)) {
            watchesCount = 0;
        }
        for (var i = 0; i < watchesCount; ++i) {
            watches.push({
                "description": data["Watch Description "+i],
                "info":        data["Watch Info "+i]
            });
        }
        model.push(watches);

        return model;
    }

    PlasmaCore.DataSource {
        id: weatherDataSource

        readonly property var currentData: data[weatherSource]

        engine: "weather"
        connectedSources: weatherSource
        interval: updateInterval * 60 * 1000
        onConnectedSourcesChanged: {
            if (weatherSource) {
                connectingToSource = true;
                plasmoid.busy = true;
                connectionTimeoutTimer.start();
            }
        }
        onCurrentDataChanged: {
            if (currentData) {
                connectionTimeoutTimer.stop();
                connectingToSource = false;
                plasmoid.busy = false;
            }
        }
    }

    Timer {
        id: connectionTimeoutTimer

        interval: 60 * 1000 // 1 min
        repeat: false
        onTriggered: {
            connectingToSource = false;
            plasmoid.busy = false;
            // TODO: inform user
            var sourceTokens = weatherSource.split("|");
            var foo = i18n("Weather information retrieval for %1 timed out.", sourceTokens.value(2));
        }
    }

    // workaround for now to ensure "Please configure" tooltip
    // TODO: remove when configurationRequired works
    Plasmoid.icon: needsConfiguration ? "configure" : generalModel.currentConditionIconName
    Plasmoid.toolTipMainText: needsConfiguration ? i18nc("@info:tooltip", "Please configure") : generalModel.location
    Plasmoid.toolTipSubText: {
        if (!generalModel.location) {
            return "";
        }
        var tooltips = [];
        var temperature = plasmoid.nativeInterface.temperatureShownInTooltip ? observationModel.temperature : null;
        if (observationModel.conditions && temperature) {
            tooltips.push(i18nc("weather condition + temperature",
                                "%1 %2", observationModel.conditions, temperature));
        } else if (observationModel.conditions || temperature) {
            tooltips.push(observationModel.conditions || temperature);
        }
        if (plasmoid.nativeInterface.windShownInTooltip && observationModel.windSpeed) {
            if (observationModel.windDirection) {
                if (observationModel.windGust) {
                    tooltips.push(i18nc("winddirection windspeed (windgust)", "%1 %2 (%3)",
                                        observationModel.windDirection, observationModel.windSpeed, observationModel.windGust));
                } else {
                    tooltips.push(i18nc("winddirection windspeed", "%1 %2",
                                        observationModel.windDirection, observationModel.windSpeed));
                }
            } else {
                tooltips.push(observationModel.windSpeed);
            }
        }
        if (plasmoid.nativeInterface.pressureShownInTooltip && observationModel.pressure) {
            if (observationModel.pressureTendency) {
                tooltips.push(i18nc("pressure (tendency)", "%1 (%2)",
                                    observationModel.pressure, observationModel.pressureTendency));
            } else {
                tooltips.push(observationModel.pressure);
            }
        }
        if (plasmoid.nativeInterface.humidityShownInTooltip && observationModel.humidity) {
            tooltips.push(i18n("Humidity: %1", observationModel.humidity));
        }

        return tooltips.join("\n");
    }

    Plasmoid.compactRepresentation: CompactRepresentation {
        generalModel: root.generalModel
        observationModel: root.observationModel
    }

    Plasmoid.fullRepresentation: FullRepresentation {
        generalModel: root.generalModel
        observationModel: root.observationModel
    }

    Binding {
        target: plasmoid.nativeInterface
        property: "needsToBeSquare"
        value: plasmoid.containmentType === PlasmaCore.Types.CustomEmbeddedContainment
    }
}
